/*
 * Copyright (C) 2019 Peng fei Pan <panpfpanpf@outlook.me>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package me.panpf.sketch.util;


import me.panpf.sketch.Initializer;
import me.panpf.sketch.SketchImageView;
import me.panpf.sketch.SketchView;
import me.panpf.sketch.datasource.DataSource;
import me.panpf.sketch.decode.ImageDecodeUtils;
import me.panpf.sketch.decode.ImageOrientationCorrector;
import me.panpf.sketch.decode.ImageType;
import me.panpf.sketch.drawable.SketchLoadingDrawable;
import me.panpf.sketch.request.DisplayRequest;
import me.panpf.sketch.uri.UriModel;
import me.panpf.sketch.zoom.PixmapBuild;
import me.panpf.sketch.zoom.Size;
import me.panpf.sketch.zoom.block.Block;
import ohos.aafwk.ability.RunningProcessInfo;
import ohos.agp.components.Component;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.opengl.*;
import ohos.agp.render.render3d.Engine;
import ohos.agp.render.render3d.impl.AgpEngineFactory;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Rect;
import ohos.agp.utils.TextTool;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.app.IAbilityManager;
import ohos.bundle.AbilityInfo;
import ohos.bundle.ApplicationInfo;
import ohos.bundle.BundleInfo;
import ohos.bundle.IBundleManager;
import ohos.data.usage.DataUsage;
import ohos.data.usage.MountState;
import ohos.data.usage.StatVfs;
import ohos.eventhandler.EventRunner;
import ohos.global.resource.RawFileDescriptor;
import ohos.global.resource.Resource;
import ohos.global.resource.ResourceManager;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.ImageInfo;
import ohos.media.image.common.PixelFormat;
import ohos.os.ProcessManager;
import ohos.rpc.RemoteException;
import ohos.utils.geo.Point;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.*;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;

import static ohos.agp.components.ComponentContainer.LayoutConfig.MATCH_CONTENT;
import static ohos.agp.components.ComponentContainer.LayoutConfig.MATCH_PARENT;
import static ohos.bundle.IBundleManager.GET_BUNDLE_WITH_ABILITIES;

@SuppressWarnings("WeakerAccess")
public class SketchUtils {

    private static final float[] MATRIX_VALUES = new float[9];

    /**
     * Read apk file icon. Although the PackageManager will cache the icon, the bitmap returned by this method every time
     *
     * @param context     {@link Context}
     * @param apkFilePath Apk file path
     * @return PixelMap
     */
    @Nullable
    public static PixelMap readApkIcon(@NotNull Context context, @NotNull String apkFilePath) {
        IBundleManager packageManager = context.getBundleManager();
        try {
            BundleInfo packageInfo = packageManager.getBundleArchiveInfo(apkFilePath, GET_BUNDLE_WITH_ABILITIES);
            List<AbilityInfo> infos = packageInfo.getAbilityInfos();
            for (int i = 0; i < infos.size(); i++) {
                ApplicationInfo applicationInfo = packageManager.getApplicationInfo(infos.get(i).bundleName, GET_BUNDLE_WITH_ABILITIES, 0);
                String iconName = applicationInfo.getIcon();
                if (iconName.equals(infos.get(i).getIconPath())) {
                    return packageManager.getAbilityIcon(context.getBundleName(), infos.get(i).className);
                }
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 清空目录
     *
     * @param dir
     * @return true：成功
     */
    public static boolean cleanDir(@Nullable File dir) {
        if (dir == null || !dir.exists() || !dir.isDirectory()) {
            return true;
        }

        File[] files = dir.listFiles();
        boolean cleanSuccess = true;
        if (files != null) {
            for (File tempFile : files) {
                if (tempFile.isDirectory()) {
                    cleanSuccess &= cleanDir(tempFile);
                }
                cleanSuccess &= tempFile.delete();
            }
        }
        return cleanSuccess;
    }

    /**
     * 删除给定的文件，如果当前文件是目录则会删除其包含的所有的文件或目录
     *
     * @param file 给定的文件
     * @return true：删除成功；false：删除失败
     */
    public static boolean deleteFile(@Nullable File file) {
        if (file == null || !file.exists()) {
            return true;
        }

        if (file.isDirectory()) {
            cleanDir(file);
        }
        return file.delete();
    }


    /**
     * 检查文件名是不是指定的后缀
     *
     * @param fileName 例如：test.txt
     * @param suffix   例如：.txt
     * @return boolean
     */
    public static boolean checkSuffix(@Nullable String fileName, @NotNull String suffix) {
        if (fileName == null) {
            return false;
        }

        // 截取后缀名
        String fileNameSuffix;
        int lastIndex = fileName.lastIndexOf(".");
        if (lastIndex > -1) {
            fileNameSuffix = fileName.substring(lastIndex);
        } else {
            return false;
        }

        return suffix.equalsIgnoreCase(fileNameSuffix);
    }

    /**
     * 关闭资源
     *
     * @param closeable 可关闭的对象
     */
    public static void close(@Nullable Closeable closeable) {
        if (closeable == null) {
            return;
        }

        if (closeable instanceof OutputStream) {
            try {
                ((OutputStream) closeable).flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try {
            closeable.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void close(@Nullable RawFileDescriptor fileDescriptor) {
        if (fileDescriptor == null) {
            return;
        }

        try {
            fileDescriptor.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Deprecated
    public static boolean isGifImage(@Nullable PixelMapElement pixelMapElement) {
        return false;
    }

    /**
     * 判断是否是gif图片
     *
     * @param imageSource 图片资源
     * @return 是否是gif图片
     */
    public static boolean isGifImage(@Nullable ImageSource imageSource) {
        if (imageSource == null) return false;
        //如果有超过1帧的图片就认为是gif图
        return imageSource.getImageInfo(1) != null;
    }

    /**
     * 判断是否是gif图片
     *
     * @param uri 图片uri
     * @return 是否是gif图片
     */
    public static boolean isGifImage(String uri) {
        if (TextTool.isNullOrEmpty(uri))
            return false;
        return uri.endsWith(".gif");
    }

    /**
     * 控件布局格式化
     *
     * @param size 尺寸
     * @return 布局格式化字符串
     */
    @NotNull
    public static String viewLayoutFormatted(int size) {
        if (size >= 0) {
            return String.valueOf(size);
        } else if (size == MATCH_PARENT) {
            return "MATCH_PARENT";
        } else if (size == MATCH_CONTENT) {
            return "WRAP_CONTENT";
        } else {
            return "Unknown";
        }
    }

    /**
     * 是不是主线程
     *
     * @return boolean
     */
    public static boolean isMainThread() {
        return EventRunner.getMainEventRunner().isCurrentRunnerThread();
    }


    /**
     * 获取当前进程的名字
     *
     * @param context
     * @return String
     */
    @Nullable
    public static String getProcessName(Context context) {
        int pid = ProcessManager.getPid();
        IAbilityManager am = context.getAbilityManager();
        List<RunningProcessInfo> runningApps = am != null ? am.getAllRunningProcesses() : null;
        if (runningApps == null) {
            return null;
        }
        for (RunningProcessInfo procInfo : runningApps) {
            if (procInfo.getPid() == pid) {
                return procInfo.getProcessName();
            }
        }
        return null;
    }

    /**
     * 当前进程是不是主进程
     *
     * @param context
     * @return boolean
     */
    public static boolean isMainProcess(@NotNull Context context) {
        return context.getBundleName().equalsIgnoreCase(getProcessName(context));
    }


    /**
     * 获取短的当前进程的名字，例如进程名字为 com.my.app:push，那么短名字就是 :push
     *
     * @param context
     * @return String
     */
    @Nullable
    public static String getSimpleProcessName(@NotNull Context context) {
        String processName = getProcessName(context);
        if (processName == null) {
            return null;
        }
        String packageName = context.getBundleName();
        int lastIndex = processName.lastIndexOf(packageName);
        return lastIndex != -1 ? processName.substring(lastIndex + packageName.length()) : null;
    }

    /**
     * 获取 app 缓存目录，优先考虑 sdcard 上的缓存目录
     *
     * @param context
     * @return File
     */
    @Nullable
    public static File getAppCacheDir(@NotNull Context context) {
        File appCacheDir = null;
        if (MountState.DISK_MOUNTED.equals(DataUsage.getDiskMountedStatus())) {
            appCacheDir = context.getExternalCacheDir();
        }
        if (appCacheDir == null) {
            appCacheDir = context.getCacheDir();
        }
        return appCacheDir;
    }

    /**
     * 获取给定目录的可用大小
     *
     * @param dir
     * @return long
     */
    public static long getAvailableBytes(@NotNull File dir) {
        if (!dir.exists() && !dir.mkdirs()) {
            return 0;
        }
        StatVfs dirStatFs;
        try {
            dirStatFs = new StatVfs(dir.getPath());
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return 0;
        }
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
        return dirStatFs.getAvailableSpace();
//        } else {
//            //noinspection deprecation
//            return (long) dirStatFs.getAvailableBlocks() * dirStatFs.getBlockSize();
//        }
    }

    /**
     * 获取给定目录的总大小
     *
     * @param dir
     * @return long
     */
    public static long getTotalBytes(@NotNull File dir) {
        if (!dir.exists() && !dir.mkdirs()) {
            return 0;
        }
        StatVfs dirStatFs;
        try {
            dirStatFs = new StatVfs(dir.getPath());
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return 0;
        }
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
        return dirStatFs.getSpace();
//        } else {
//            //noinspection deprecation
//            return (long) dirStatFs.getBlockCount() * dirStatFs.getBlockSize();
//        }
    }

    /**
     * 获取所有可用的 sdcard 的路径
     *
     * @param context
     * @return 所有可用的 sdcard 的路径
     */
    @Nullable
    public static String[] getAllAvailableSdcardPath(@NotNull Context context) {
//        String[] paths;
//        Method getVolumePathsMethod;
//        try {
//            getVolumePathsMethod = StorageManager.class.getMethod("getVolumePaths");
//        } catch (NoSuchMethodException e) {
//            SLog.e("getAllAvailableSdcardPath", "not found StorageManager.getVolumePaths() method");
//            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
//                return new String[]{Environment.getExternalStorageDirectory().getPath()};
//            } else {
//                return null;
//            }
//        }
//
//        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
//        try {
//            paths = (String[]) getVolumePathsMethod.invoke(sm);
//        } catch (IllegalAccessException e) {
//            e.printStackTrace();
//            return null;
//        } catch (InvocationTargetException e) {
//            e.printStackTrace();
//            return null;
//        }
//
//        if (paths == null || paths.length == 0) {
//            return null;
//        }
//
//        // 去掉不可用的存储器
//        List<String> storagePathList = new LinkedList<>();
//        Collections.addAll(storagePathList, paths);
//        Iterator<String> storagePathIterator = storagePathList.iterator();
//
//        String path;
//        Method getVolumeStateMethod = null;
//        while (storagePathIterator.hasNext()) {
//            path = storagePathIterator.next();
//            if (getVolumeStateMethod == null) {
//                try {
//                    getVolumeStateMethod = StorageManager.class.getMethod("getVolumeState", String.class);
//                } catch (NoSuchMethodException e) {
//                    e.printStackTrace();
//                    return null;
//                }
//            }
//            String status;
//            try {
//                status = (String) getVolumeStateMethod.invoke(sm, path);
//            } catch (Exception e) {
//                e.printStackTrace();
//                storagePathIterator.remove();
//                continue;
//            }
//            if (!(Environment.MEDIA_MOUNTED.equals(status) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status))) {
//                storagePathIterator.remove();
//            }
//        }
//        return storagePathList.toArray(new String[0]);

        return new String[]{"/storage/emulated/0"};
    }

    /**
     * 获取目录名加进程名
     *
     * @param context 上下文
     * @param dirName 目录名称
     * @return 目录名加进程名
     */
    @NotNull
    public static String appendProcessName(@NotNull Context context, @NotNull String dirName) {
        // 目录名字加上进程名字的后缀，不同的进程不同目录，以兼容多进程
        String simpleProcessName = SketchUtils.getSimpleProcessName(context);
        if (simpleProcessName != null) {
            try {
                dirName += URLEncoder.encode(simpleProcessName, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

        return dirName;
    }

    /**
     * 获取默认缓存目录
     *
     * @param context           上下文
     * @param dirName           目录名称
     * @param compatManyProcess 兼容多进程
     * @return 默认缓存目录
     */
    @NotNull
    public static File getDefaultSketchCacheDir(@NotNull Context context, @NotNull String dirName, boolean compatManyProcess) {
        File appCacheDir = SketchUtils.getAppCacheDir(context);
        return new File(appCacheDir, compatManyProcess ? appendProcessName(context, dirName) : dirName);
    }

    /**
     * testCreateFile
     *
     * @param cacheDir
     * @return boolean
     * @throws Exception
     */
    public static boolean testCreateFile(@NotNull File cacheDir) throws Exception {
        File parentDir = cacheDir;
        while (parentDir != null) {
            // 先向上找到一个已存在的目录
            if (!parentDir.exists()) {
                parentDir = cacheDir.getParentFile();
                continue;
            }

            // 然后尝试创建文件
            File file = new File(parentDir, "create_test.temp");

            // 已存在就先删除，删除失败就抛异常
            if (file.exists() && !file.delete()) {
                throw new Exception("Delete old test file failed: " + file.getPath());
            }

            //noinspection ResultOfMethodCallIgnored
            if (file.createNewFile()) {
            }

            if (file.exists()) {
                if (file.delete()) {
                    return true;
                } else {
                    throw new Exception("Delete test file failed: " + file.getPath());
                }
            } else {
                return false;
            }
        }

        return false;
    }

    /**
     * 创建缓存目录，会优先在 sdcard 上创建
     *
     * @param dirName            目录名称
     * @param compatManyProcess  目录名称是否加上进程名
     * @param minSpaceSize       最小空间
     * @param cleanOnNoSpace     空间不够用时就尝试清理一下
     * @param cleanOldCacheFiles 清除旧的缓存文件
     * @param expandNumber       当 dirName 无法使用时就会尝试 dirName1、dirName2、dirName3...
     * @param context
     * @return 你应当以返回的目录为最终可用的目录
     * @throws NoSpaceException          可用空间小于 minSpaceSize；UnableCreateDirException：无法创建缓存目录；UnableCreateFileException：无法在缓存目录中创建文件
     * @throws UnableCreateDirException
     * @throws UnableCreateFileException
     */
    @NotNull
    public static File buildCacheDir(@NotNull Context context, @NotNull String dirName, boolean compatManyProcess, long minSpaceSize, boolean cleanOnNoSpace,
                                     boolean cleanOldCacheFiles, int expandNumber) throws NoSpaceException, UnableCreateDirException, UnableCreateFileException {
        List<File> appCacheDirs = new LinkedList<>();

        String[] sdcardPaths = getAllAvailableSdcardPath(context);
        if (sdcardPaths != null && sdcardPaths.length > 0) {
            for (String sdcardPath : sdcardPaths) {
                appCacheDirs.add(new File(sdcardPath, "Android" + File.separator + "data" + File.separator + context.getBundleName() + File.separator + "cache"));
            }
        }
        appCacheDirs.add(context.getCacheDir());

        String diskCacheDirName = compatManyProcess ? appendProcessName(context, dirName) : dirName;

        NoSpaceException noSpaceException = null;
        UnableCreateFileException unableCreateFileException = null;
        File diskCacheDir = null;
        int expandCount;

        for (File appCacheDir : appCacheDirs) {
            expandCount = 0;
            while (expandCount <= expandNumber) {
                diskCacheDir = new File(appCacheDir, diskCacheDirName + (expandCount > 0 ? expandCount : ""));

                if (diskCacheDir.exists()) {
                    // 目录已存在的话就尝试清除旧的缓存文件
                    if (cleanOldCacheFiles) {
                        File journalFile = new File(diskCacheDir, DiskLruCache.JOURNAL_FILE);
                        if (!journalFile.exists()) {
                            cleanDir(diskCacheDir);
                        }
                    }
                } else {
                    // 目录不存在就创建，创建结果返回false后检查还是不存在就说明创建失败
                    if (!diskCacheDir.mkdirs() && !diskCacheDir.exists()) {
                        expandCount++;
                        continue;
                    }
                }

                // 检查空间，少于minSpaceSize就不能用了
                long availableBytes = getAvailableBytes(diskCacheDir);
                if (availableBytes < minSpaceSize) {
                    // 空间不够用的时候直接清空，然后再次计算可用空间
                    if (cleanOnNoSpace) {
                        cleanDir(diskCacheDir);
                        availableBytes = getAvailableBytes(diskCacheDir);
                    }

                    // 依然不够用，那不好意思了
                    if (availableBytes < minSpaceSize) {
//                        String availableFormatted = Formatter.formatFileSize(context, availableBytes);
//                        String minSpaceFormatted = Formatter.formatFileSize(context, minSpaceSize);
//                        noSpaceException = new NoSpaceException("Need " + availableFormatted + ", with only " + minSpaceFormatted + " in " + diskCacheDir.getPath());
                        noSpaceException = new NoSpaceException("空间不够用了 !!!! " + diskCacheDir.getPath());
                        break;
                    }
                }

                // 创建文件测试
                try {
                    if (testCreateFile(diskCacheDir)) {
                        return diskCacheDir;
                    } else {
                        unableCreateFileException = new UnableCreateFileException("Unable create file in " + diskCacheDir.getPath());
                        expandCount++;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    unableCreateFileException = new UnableCreateFileException(e.getClass().getSimpleName() + ": " + e.getMessage());
                    expandCount++;
                }
            }
        }

        if (noSpaceException != null) {
            throw noSpaceException;
        } else if (unableCreateFileException != null) {
            throw unableCreateFileException;
        } else {
            throw new UnableCreateDirException("Unable create dir: " + (diskCacheDir != null ? diskCacheDir.getPath() : "null"));
        }
    }


    /**
     * 从 {@link SketchView} 上查找 {@link DisplayRequest}
     *
     * @param sketchView
     * @return DisplayRequest
     */
    @Nullable
    public static DisplayRequest findDisplayRequest(@Nullable SketchView sketchView) {
        if (sketchView != null) {
            final Element drawable = sketchView.getDrawable();
            if (drawable instanceof SketchLoadingDrawable) {
                return ((SketchLoadingDrawable) drawable).getRequest();
            }
        }
        return null;
    }

    /**
     * 根据给定的信息，生成最终的图片信息
     *
     * @param type            类型
     * @param imageWidth      图片宽
     * @param imageHeight     图片高
     * @param mimeType        图片格式
     * @param exifOrientation 图片方向
     * @param bitmap          {@link PixelMap}
     * @param byteCount       {@link PixelMap} 占用字节数
     * @param key
     * @return String
     */
    @NotNull
    public static String makeImageInfo(@Nullable String type, int imageWidth, int imageHeight, @Nullable String mimeType,
                                       int exifOrientation, @Nullable PixmapBuild bitmap, long byteCount, @Nullable String key) {
        if (bitmap == null) {
            return "Unknown";
        }

        type = TextUtils.isEmpty(type) ? "Bitmap" : type;
        String hashCode = Integer.toHexString(bitmap.hashCode());
        String config = bitmap.getPixelMap().getImageInfo() != null ? bitmap.getPixelMap().getImageInfo().pixelFormat.name() : null;
        String finalKey = key != null ? String.format(", key=%s", key) : "";
        return String.format(Locale.US, "%s(image=%dx%d,%s,%s, bitmap=%dx%d,%s,%d,%s%s)",
                type, imageWidth, imageHeight, mimeType, ImageOrientationCorrector.toName(exifOrientation),
                bitmap.getPixelMap().getImageInfo().size.width, bitmap.getPixelMap().getImageInfo().size.height, config, byteCount, hashCode,
                finalKey);
    }

    /**
     * 获取最终的图片
     *
     * @param imageSource 图片资源
     * @return 最终的图片
     */
    @Nullable
    public static Element getLastDrawable(@Nullable ImageSource imageSource) {
        if (imageSource == null) return null;
        int i = 1;
        ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
        decodingOptions.allowPartialImage = true;
        PixelMap result = null;
        PixelMap temp;
        try {
            while ((temp = imageSource.createPixelmap(i, decodingOptions)) != null) {
                result = temp;
                i++;
            }
        } catch (Exception e) {
        }
        return result != null ? new PixelMapElement(result) : null;
    }

    @Deprecated
    @Nullable
    public static Element getLastDrawable(@Nullable Element element) {
//        if (element instanceof PixelMapElement) {
//            PixelMap pixels = ((PixelMapElement) element).getPixelMap();
//            ByteBuffer dst = ByteBuffer.allocate((int) pixels.getPixelBytesNumber());
//            pixels.readPixels(dst);
//
//            ImageSource imageSource = ImageSource.create(dst, null);
//            if (imageSource == null) return null;
//            PixelMap pixelMap = null;
//            int i = 1;
//            try {
//                ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
//                decodingOptions.allowPartialImage = true;
//                PixelMap temp;
//                while ((temp = imageSource.createPixelmap(i, decodingOptions)) != null) {
//                    pixelMap = temp;
//                    i++;
//                }
//            } catch (Exception e) {
//            }
//            return pixelMap == null ? null : new PixelMapElement(pixelMap);
//        } else {
        return element;
//        }
    }

    /**
     * 从 {@link Matrix} 中获取缩放比例
     *
     * @param matrix
     * @return float
     */
    public static float getMatrixScale(@NotNull Matrix matrix) {
        synchronized (MATRIX_VALUES) {
            matrix.getElements(MATRIX_VALUES);
            final float scaleX = MATRIX_VALUES[0];
//            final float scaleX = MATRIX_VALUES[Matrix.MSCALE_X];
            final float skewY = MATRIX_VALUES[3];
//            final float skewY = MATRIX_VALUES[Matrix.MSKEW_Y];
            return (float) Math.sqrt((float) Math.pow(scaleX, 2d) + Math.pow(skewY, 2d));
        }
    }


    /**
     * 从 {@link Matrix} 中获取旋转角度
     *
     * @param matrix
     * @return int
     */
    public static int getMatrixRotateDegrees(@NotNull Matrix matrix) {
        synchronized (MATRIX_VALUES) {
            matrix.getElements(MATRIX_VALUES);
            final float skewX = matrix.getSkewX(); //MATRIX_VALUES[Matrix.MSKEW_X];
            final float scaleX = matrix.getScaleX(); //MATRIX_VALUES[Matrix.MSCALE_X];
            final int degrees = (int) Math.round(Math.atan2(skewX, scaleX) * (180 / Math.PI));
            if (degrees < 0) {
                return Math.abs(degrees);
            } else if (degrees > 0) {
                return 360 - degrees;
            } else {
                return 0;
            }
        }
    }

    /**
     * 从 {@link Matrix} 中获取偏移位置
     *
     * @param matrix
     * @param point
     */
    public static void getMatrixTranslation(@NotNull Matrix matrix, @NotNull Point point) {
        synchronized (MATRIX_VALUES) {
            matrix.getElements(MATRIX_VALUES);
//            point.x = MATRIX_VALUES[Matrix.MTRANS_X];
//            point.y = MATRIX_VALUES[Matrix.MTRANS_Y];
            point.modify(matrix.getTranslateX(), matrix.getTranslateY());
        }
    }


    /**
     * 获取 OpenGL 的版本
     *
     * @param context
     * @return String
     */
    @NotNull
    public static String getOpenGLVersion(@NotNull Context context) {
        IAbilityManager am = context.getAbilityManager();
        return am != null ? am.getDeviceConfigInfo().getDeviceGLESVersion() + "" : "";
    }


    /**
     * 获取 OpenGL 所允许的图片的最大尺寸(单边长)
     *
     * @return int
     */
    public static int getOpenGLMaxTextureSize() {
        int maxTextureSize = 0;
        try {
            maxTextureSize = getOpenGLMaxTextureSizeJB1();
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (maxTextureSize == 0) {
            maxTextureSize = 4096;
        }

        return maxTextureSize;
    }

    /**
     * getOpenGLMaxTextureSizeJB1
     *
     * @return int
     */
    private static int getOpenGLMaxTextureSizeJB1() {
        // Then get a hold of the default display, and initialize.
        // This could get more complex if you have to deal with devices that could have multiple displays,
        // but will be sufficient for a typical phone/tablet:
        EGLDisplay dpy = EGL.eglGetDisplay(EGL.EGL_DEFAULT_DISPLAY);
        int[] vers = new int[2];
        EGL.eglInitialize(dpy, vers, vers);

        // Next, we need to find a config. Since we won't use this context for rendering,
        // the exact attributes aren't very critical:
        int[] configAttr = {
                EGL.EGL_COLOR_BUFFER_TYPE, EGL.EGL_RGB_BUFFER,
                EGL.EGL_LEVEL, 0,
                EGL.EGL_RENDERABLE_TYPE,/* EGL.EGL_OPENGL_ES2_BIT=4*/4,
                EGL.EGL_SURFACE_TYPE, EGL.EGL_PBUFFER_BIT,
                EGL.EGL_NONE
        };
        EGLConfig[] configs = new EGLConfig[1];
        int[] numConfig = new int[1];
        EGL.eglChooseConfig(dpy, configAttr,
                configs, 1, numConfig);
        //noinspection StatementWithEmptyBody
        if (numConfig[0] == 0) {
            // TROUBLE! No config found.
        }
        EGLConfig config = configs[0];

        // To make a context current, which we will need later,
        // you need a rendering surface, even if you don't actually plan to render.
        // To satisfy this requirement, create a small offscreen (Pbuffer) surface:
        int[] surfAttr = {
                EGL.EGL_WIDTH, 64,
                EGL.EGL_HEIGHT, 64,
                EGL.EGL_NONE
        };
        EGLSurface surf = EGL.eglCreatePbufferSurface(dpy, config, surfAttr);

        // Next, create the context:   EGL.EGL_CONTEXT_CLIENT_VERSION=12440
        int[] ctxAttrib = {
                12440, 2,
                EGL.EGL_NONE
        };
        EGLContext ctx = EGL.eglCreateContext(dpy, config, EGL.EGL_NO_CONTEXT, ctxAttrib);

        // Ready to make the context current now:
        EGL.eglMakeCurrent(dpy, surf, surf, ctx);

        // If all of the above succeeded (error checking was omitted), you can make your OpenGL calls now:
        int[] maxSize = new int[1];
        GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize);

        // Once you're all done, you can tear down everything:
        EGL.eglMakeCurrent(dpy, EGL.EGL_NO_SURFACE, EGL.EGL_NO_SURFACE,
                EGL.EGL_NO_CONTEXT);
        EGL.eglDestroySurface(dpy, surf);
        EGL.eglDestroyContext(dpy, ctx);
        EGL.eglTerminate(dpy);

        return maxSize[0];
    }

//    private static int getOpenGLMaxTextureSizeBase() {
//        // In JELLY_BEAN will collapse
//        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
//            return 0;
//        }
//
//        EGL10 egl = (EGL10) EGLContext.getEGL();
//
//        EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
//        int[] vers = new int[2];
//        egl.eglInitialize(dpy, vers);
//
//        int[] configAttr = {
//                EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER,
//                EGL10.EGL_LEVEL, 0,
//                EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
//                EGL10.EGL_NONE
//        };
//        EGLConfig[] configs = new EGLConfig[1];
//        int[] numConfig = new int[1];
//        egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig);
//        //noinspection StatementWithEmptyBody
//        if (numConfig[0] == 0) {
//            // TROUBLE! No config found.
//        }
//        EGLConfig config = configs[0];
//
//        int[] surfAttr = new int[]{
//                EGL10.EGL_WIDTH, 64,
//                EGL10.EGL_HEIGHT, 64,
//                EGL10.EGL_NONE
//        };
//        EGLSurface surf = egl.eglCreatePbufferSurface(dpy, config, surfAttr);
//        final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;  // missing in EGL10
//        int[] ctxAttrib = {
//                EGL_CONTEXT_CLIENT_VERSION, 1,
//                EGL10.EGL_NONE
//        };
//        EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttrib);
//        egl.eglMakeCurrent(dpy, surf, surf, ctx);
//        int[] maxSize = new int[1];
//        GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
//
//        egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
//        egl.eglDestroySurface(dpy, surf);
//        egl.eglDestroyContext(dpy, ctx);
//        egl.eglTerminate(dpy);
//
//        return maxSize[0];
//    }


    /**
     * 格式化小数，可以指定保留多少位小数
     *
     * @param floatValue
     * @param newScale
     * @return float
     */
    public static float formatFloat(float floatValue, int newScale) {
        BigDecimal b = new BigDecimal(floatValue);
        return b.setScale(newScale, BigDecimal.ROUND_HALF_UP).floatValue();
    }


    /**
     * 根据图片格式型判断是否支持读取图片碎片
     *
     * @param imageType
     * @return boolean
     */
    public static boolean formatSupportBitmapRegionDecoder(@Nullable ImageType imageType) {
        return imageType == ImageType.JPEG || imageType == ImageType.PNG || imageType == ImageType.WEBP;
    }


    /**
     * 判断两个矩形是否相交
     *
     * @param rect1
     * @param rect2
     * @return boolean
     */
    public static boolean isCross(@NotNull Rect rect1, @NotNull Rect rect2) {
        return rect1.left < rect2.right && rect2.left < rect1.right && rect1.top < rect2.bottom && rect2.top < rect1.bottom;
    }


    /**
     * dp 转换成 px
     *
     * @param context
     * @param dpValue
     * @return int
     */
    public static int dp2px(@NotNull Context context, int dpValue) {
        return (int) ((dpValue * DisplayManager.getInstance().getDefaultDisplay(context).get().getRealAttributes().densityPixels) + 0.5);
    }

    /**
     * 将一个旋转了一定度数的矩形转回来（只能是 90 度的倍数）
     *
     * @param drawableSize
     * @param rect
     * @param rotateDegrees
     */
    public static void reverseRotateRect(@NotNull Rect rect, int rotateDegrees, @NotNull Size drawableSize) {
        if (rotateDegrees % 90 != 0) {
            return;
        }

        if (rotateDegrees == 90) {
            int cache = rect.bottom;
            //noinspection SuspiciousNameCombination
            rect.bottom = rect.left;
            //noinspection SuspiciousNameCombination
            rect.left = rect.top;
            //noinspection SuspiciousNameCombination
            rect.top = rect.right;
            rect.right = cache;

            rect.top = drawableSize.getHeight() - rect.top;
            rect.bottom = drawableSize.getWidth() - rect.bottom;
        } else if (rotateDegrees == 180) {
            int cache = rect.right;
            rect.right = rect.left;
            rect.left = cache;

            cache = rect.bottom;
            rect.bottom = rect.top;
            rect.top = cache;

            rect.top = drawableSize.getHeight() - rect.top;
            rect.bottom = drawableSize.getHeight() - rect.bottom;

            rect.left = drawableSize.getWidth() - rect.left;
            rect.right = drawableSize.getWidth() - rect.right;
        } else if (rotateDegrees == 270) {
            int cache = rect.bottom;
            //noinspection SuspiciousNameCombination
            rect.bottom = rect.right;
            //noinspection SuspiciousNameCombination
            rect.right = rect.top;
            //noinspection SuspiciousNameCombination
            rect.top = rect.left;
            rect.left = cache;

            rect.left = drawableSize.getWidth() - rect.left;
            rect.right = drawableSize.getWidth() - rect.right;
        }
    }

    /**
     * 旋转一个点（只能是 90 的倍数）
     *
     * @param drawableSize
     * @param point
     * @param rotateDegrees
     */
    public static void rotatePoint(@NotNull Point point, int rotateDegrees, @NotNull Size drawableSize) {
        if (rotateDegrees % 90 != 0) {
            return;
        }

        if (rotateDegrees == 90) {
            float newX = (float) ((double) drawableSize.getHeight() - point.getPointY());
            //noinspection SuspiciousNameCombination
            float newY = point.getPointX();
            point.modify(newX, newY);
        } else if (rotateDegrees == 180) {
            float newX = (float) ((double) drawableSize.getWidth() - point.getPointX());
            float newY = (float) ((double) drawableSize.getHeight() - point.getPointY());
            point.modify(newX, newY);
        } else if (rotateDegrees == 270) {
            //noinspection SuspiciousNameCombination
            float newX = point.getPointY();
            float newY = (float) ((double) drawableSize.getWidth() - point.getPointX());
            point.modify(newX, newY);
        }
    }

    /**
     * 生成请求 key
     *
     * @param imageUri   图片地址
     * @param uriModel
     * @param optionsKey 选项 key
     * @return String
     * @see SketchImageView#getOptionsKey()
     */
    @NotNull
    public static String makeRequestKey(@NotNull String imageUri, @NotNull UriModel uriModel, @NotNull String optionsKey) {
        if (uriModel.isConvertShortUriForKey()) {
            imageUri = SketchMD5Utils.md5(imageUri);
        }

        if (TextUtils.isEmpty(optionsKey)) {
            return imageUri;
        }

        StringBuilder builder = new StringBuilder(imageUri);
        if (imageUri.lastIndexOf("?") == -1) {
            builder.append('?');
        } else {
            builder.append('&');
        }
        builder.append("options");
        builder.append("=");
        builder.append(optionsKey);
        return builder.toString();
    }


    /**
     * 将一个碎片列表转换成字符串
     *
     * @param blockList
     * @return String
     */
    @Nullable
    public static String blockListToString(@Nullable List<Block> blockList) {
        if (blockList == null) {
            return null;
        }

        StringBuilder builder = new StringBuilder();
        builder.append("[");
        for (Block block : blockList) {
            if (builder.length() > 1) {
                builder.append(",");
            }
            builder.append("\"");
            builder.append(block.drawRect.left).append(",");
            builder.append(block.drawRect.top).append(",");
            builder.append(block.drawRect.right).append(",");
            builder.append(block.drawRect.bottom);
            builder.append("\"");
        }
        builder.append("]");
        return builder.toString();
    }


    /**
     * 根据指定的 {@link PixelMap} 配置获取合适的压缩格式
     *
     * @param config
     * @return CompressFormat
     */
    @NotNull
    public static CompressFormat bitmapConfigToCompressFormat(@Nullable PixelFormat config) {
        return config == PixelFormat.RGB_565 ?
                CompressFormat.JPEG : CompressFormat.PNG;
    }


    /**
     * 获取 {@link PixelMap} 占用内存大小，单位字节
     *
     * @param imageSource
     * @return long
     */
    public static long getByteCount(@Nullable ImageSource imageSource) {
        // bitmap.isRecycled()过滤很关键，在4.4以及以下版本当bitmap已回收时调用其getAllocationByteCount()方法将直接崩溃
        if (imageSource == null || imageSource.isReleased()) {
            return 0;
        } else /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) */ {
            PixelMap bitmap = imageSource.createPixelmap(null);
            return bitmap.getPixelBytesCapacity();
        }
//        else {
//            return bitmap.getByteCount();
//        }
    }


    /**
     * 获取 {@link PixelMap} 占用内存大小，单位字节
     *
     * @param pixelMap
     * @return long
     */
    public static long getByteCount(@Nullable PixelMap pixelMap) {
        // bitmap.isRecycled()过滤很关键，在4.4以及以下版本当bitmap已回收时调用其getAllocationByteCount()方法将直接崩溃
        if (pixelMap == null || pixelMap.isReleased()) {
            return 0;
        } else /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) */ {
            return pixelMap.getPixelBytesCapacity();
        }
//        else {
//            return bitmap.getByteCount();
//        }
    }


    /**
     * 根据宽、高和配置计算所占用的字节数
     *
     * @param width
     * @param height
     * @param config
     * @return int
     */
    public static int computeByteCount(int width, int height, @Nullable PixelFormat config) {
        return width * height * getBytesPerPixel(config);
    }


    /**
     * 获取指定配置单个像素所占的字节数
     *
     * @param config
     * @return int
     */
    public static int getBytesPerPixel(@Nullable PixelFormat config) {
        // A bitmap by decoding a gif has null "config" in certain environments.
        if (config == null) {
            config = PixelFormat.ARGB_8888;
        }

        int bytesPerPixel;
        switch (config) {
//            case ALPHA_8:
//                bytesPerPixel = 1;
//                break;
            case RGB_565:
//            case ARGB_4444:
                bytesPerPixel = 2;
                break;
            case ARGB_8888:
            default:
                bytesPerPixel = 4;
        }
        return bytesPerPixel;
    }

    /**
     * 获取修剪级别的名称
     *
     * @param level
     * @return String
     */
    @NotNull
    public static String getTrimLevelName(int level) {
        switch (level) {
            case /*ComponentCallbacks2.TRIM_MEMORY_COMPLETE*/80:
                return "COMPLETE";
            case /*ComponentCallbacks2.TRIM_MEMORY_MODERATE*/60:
                return "MODERATE";
            case /*ComponentCallbacks2.TRIM_MEMORY_BACKGROUND*/40:
                return "BACKGROUND";
            case /*ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN*/20:
                return "UI_HIDDEN";
            case /*ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL*/15:
                return "RUNNING_CRITICAL";
            case /*ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW*/10:
                return "RUNNING_LOW";
            case /*ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE*/5:
                return "RUNNING_MODERATE";
            default:
                return "UNKNOWN";
        }
    }

    /**
     * 指定的栈历史中是否存在指定的类的指定的方法
     *
     * @param stackTraceElements
     * @param cla
     * @param methodName
     * @return boolean
     */
    public static boolean invokeIn(@Nullable StackTraceElement[] stackTraceElements, @NotNull Class<?> cla, @NotNull String methodName) {
        if (stackTraceElements == null || stackTraceElements.length == 0) {
            return false;
        }

        String targetClassName = cla.getName();
        StackTraceElement element;
        String elementClassName;
        String elementMethodName;
        for (StackTraceElement stackTraceElement : stackTraceElements) {
            element = stackTraceElement;

            elementClassName = element.getClassName();
            elementMethodName = element.getMethodName();
            if (targetClassName.equals(elementClassName) && methodName.equals(elementMethodName)) {
                return true;
            }
        }

        return false;
    }


    /**
     * 对象转16进制字符串
     *
     * @param object 对象
     * @return String
     */
    @Nullable
    public static String toHexString(@Nullable Object object) {
        if (object == null) {
            return null;
        }

        return Integer.toHexString(object.hashCode());
    }

    /**
     * 计算采样大小
     *
     * @param value1       值
     * @param inSampleSize 采样大小
     * @return 采样大小
     */
    public static int calculateSamplingSize(int value1, int inSampleSize) {
        return (int) Math.ceil(value1 / (float) inSampleSize);
    }

    /**
     * 计算区域的采样大小
     *
     * @param value1       值
     * @param inSampleSize 采样大小
     * @return 区域的采样大小
     */
    public static int calculateSamplingSizeForRegion(int value1, int inSampleSize) {
        return (int) Math.floor(value1 / (float) inSampleSize);
    }

    /**
     * 生成临时文件名
     *
     * @param dataSource 数据源
     * @param uri        地址
     * @return 临时文件名
     */
    @Nullable
    public static String generatorTempFileName(@NotNull DataSource dataSource, @NotNull String uri) {
        ImageSource.SourceOptions options = new ImageSource.SourceOptions();
//        options.inJustDecodeBounds = true;
        try {
            ImageDecodeUtils.decodeBitmap(dataSource, options);
        } catch (Throwable e) {
            e.printStackTrace();
            options = null;
        }

        String uriEncode = SketchMD5Utils.md5(uri);
        if (options != null && options.formatHint != null && options.formatHint.startsWith("image/")) {
            String suffix = options.formatHint.replace("image/", "");
            return String.format("%s.%s", uriEncode, suffix);
        } else {
            return uriEncode;
        }

    }

    /**
     * findInitializer
     *
     * @param context
     * @return Initializer
     */
    @Nullable
    public static Initializer findInitializer(@NotNull Context context) {

//        ApplicationInfo appInfo;
//        try {
//            appInfo = context.getBundleManager().getApplicationInfo(context.getBundleName(), IBundleManager.GET_APPLICATION_INFO_WITH_PERMISSION);
//        } catch (RemoteException e) {
//            e.printStackTrace();
//            return null;
//        }
//
//        String initializerClassName = null;
//        if (appInfo.abilityInfos != null) {
//            for (AbilityInfo key : appInfo.abilityInfos) {
//                if (Sketch.META_DATA_KEY_INITIALIZER.equals(appInfo.metaData.get(key))) {
//                    initializerClassName = key;
//                    break;
//                }
//            }
//        }
//        if (TextUtils.isEmpty(initializerClassName)) {
//            return null;
//        }
//
//        Class<?> initializerClass;
//        try {
//            initializerClass = Class.forName(initializerClassName);
//        } catch (ClassNotFoundException e) {
//            e.printStackTrace();
//            return null;
//        }
//        if (!Initializer.class.isAssignableFrom(initializerClass)) {
//            return null;
//        }
//        //noinspection TryWithIdenticalCatches
//        try {
//            return (Initializer) initializerClass.newInstance();
//        } catch (InstantiationException e) {
//            e.printStackTrace();
//        } catch (IllegalAccessException e) {
//            e.printStackTrace();
//        }
        return null;
    }

    /**
     * 生成文件 uri 的磁盘缓存 key，关键在于要在 uri 的后面加上文件的修改时间来作为缓存 key，这样当文件发生变化时能及时更新缓存
     *
     * @param uri      文件 uri
     * @param filePath 文件路径，要获取文件的修改时间
     * @return 文件 uri 的磁盘缓存 key
     */
    @NotNull
    public static String createFileUriDiskCacheKey(@NotNull String uri, @NotNull String filePath) {
        File file = new File(filePath);
        if (file.exists()) {
            long lastModifyTime = file.lastModified();
            // 这里必须用 uri 连接修改时间，不能用 filePath，因为使用 filePath 的话当同一个文件可以用于多种 uri 时会导致磁盘缓存错乱
            return uri + "." + lastModifyTime;
        } else {
            return uri;
        }
    }

    /**
     * 发布动画
     *
     * @param view     控件
     * @param runnable 执行任务的线程
     */
    public static void postOnAnimation(@NotNull Component view, @NotNull Runnable runnable) {
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
//            view.postOnAnimation(runnable);
//        } else {
//            view.postDelayed(runnable, 1000 / 60);
//        }
        Engine engine = AgpEngineFactory.createEngine().get();
        engine.runInRenderThread(runnable);
    }

    public static int getPointerIndex(int action) {
//        return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
        throw new NullPointerException();
    }

    /**
     * Match MimeType
     *
     * @param template For example: application/*
     * @param mimeType For example: application/zip
     * @return boolean
     */
    public static boolean matchMimeType(@NotNull String template, @Nullable String mimeType) {
        String[] templateItems = template.split("/");
        String[] mimeItems = (mimeType != null ? mimeType : "").split("/");
        boolean result = true;
        if (templateItems.length > 0 && templateItems.length == mimeItems.length) {
            for (int index = 0; index < templateItems.length; index++) {
                String templateItem = templateItems[index].trim();
                String mimeItem = mimeItems[index].trim();
                result = "*".equals(templateItem) || templateItem.toLowerCase(Locale.ENGLISH).equals(mimeItem.toLowerCase());
                if (!result) {
                    break;
                }
            }
        } else {
            result = false;
        }
        return result;
    }

    public enum CompressFormat {
        JPEG(0),
        PNG(1),
        WEBP(2);

        final int nativeInt;

        CompressFormat(int nativeInt) {
            this.nativeInt = nativeInt;
        }
    }
}
